package com.glodon.paas.registry.util;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v1CertificateBuilder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.ContentVerifierProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Date;

/**
 * X.509 certificate utils class based on Bouncycastal lib.
 *
 * @author Don Li
 */
public class X509CertUtils {

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    /**
     * generate the CA's certificate
     */
    public static X509Certificate createMasterCert(String issuer, PrivateKey privKey, String subject, PublicKey pubKey, Date notBefore, Date notAfter, BigInteger serialNumber) throws Exception {
        X509v1CertificateBuilder v1CertBuilder = new JcaX509v1CertificateBuilder(
                new X500Name(issuer),
                serialNumber,
                notBefore,
                notAfter,
                new X500Name(subject),
                pubKey);

        ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privKey);
        X509CertificateHolder cert = v1CertBuilder.build(contentSigner);
        return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(cert);
    }

    /**
     * generate an intermediate certificate signed by CA
     */
    public static X509Certificate createIntermediateCert(X509Certificate caCert, PrivateKey caPrivKey, String subject, PublicKey pubKey, Date notBefore, Date notAfter, BigInteger serialNumber) throws Exception {
        // create the certificate - version 3
        X509v3CertificateBuilder v3CertBuilder = new JcaX509v3CertificateBuilder(
                JcaX500NameUtil.getIssuer(caCert),
                serialNumber,
                notBefore,
                notAfter,
                new X500Name(subject),
                pubKey);
        // add extensions
        JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();
        v3CertBuilder.addExtension(Extension.subjectKeyIdentifier, false, utils.createSubjectKeyIdentifier(pubKey));
        v3CertBuilder.addExtension(Extension.authorityKeyIdentifier, false, utils.createAuthorityKeyIdentifier(caCert));
        v3CertBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));

        ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(caPrivKey);
        X509CertificateHolder cert = v3CertBuilder.build(contentSigner);
        return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(cert);
    }

    /**
     * generate a certificate signed by our CA's intermediate certficate
     */
    public static X509Certificate createCert(X509Certificate issuerCert, String subject, Date notBefore, Date notAfter, PublicKey pubKey, PrivateKey caPrivKey, PublicKey caPubKey, BigInteger serialNumber) throws Exception {
        // create the certificate - version 3
        X509v3CertificateBuilder v3CertBuilder = new JcaX509v3CertificateBuilder(
                JcaX500NameUtil.getSubject(issuerCert),
                serialNumber,
                notBefore,
                notAfter,
                new X500Name(subject),
                pubKey);
        // add the extensions
        JcaX509ExtensionUtils utils = new JcaX509ExtensionUtils();
        v3CertBuilder.addExtension(Extension.subjectKeyIdentifier, false, utils.createSubjectKeyIdentifier(pubKey));
        v3CertBuilder.addExtension(Extension.authorityKeyIdentifier, false, utils.createAuthorityKeyIdentifier(caPubKey));

        X509CertificateHolder cert = v3CertBuilder.build(new JcaContentSignerBuilder("SHA1withRSA").setProvider(BouncyCastleProvider.PROVIDER_NAME).build(caPrivKey));
        return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(cert);
    }

    /**
     * verify a certificate's signature with its issuer's certficate
     */
    public static boolean isSignatureValid(X509Certificate cliCert, X509Certificate issuerCert) throws Exception {
        ContentVerifierProvider verifierProvider = new JcaContentVerifierProviderBuilder().build(issuerCert);
        X509CertificateHolder cliCertHolder = new JcaX509CertificateHolder(cliCert);
        return cliCertHolder.isSignatureValid(verifierProvider);
    }

    /**
     * check if the certificate is expired.
     */
    public static boolean isValidOn(Date date, X509Certificate cert) throws Exception {
        X509CertificateHolder certHolder = new JcaX509CertificateHolder(cert);
        return certHolder.isValidOn(date);
    }

    /**
     * store one certificate to a PKCS12 format file
     */
    public static void storeCertAsPkcs12(X509Certificate cert, String alias, File file, char[] password) throws Exception {
        KeyStore store = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
        store.load(null, null); // init the keystore
        store.setCertificateEntry(alias, cert);
        store.store(new FileOutputStream(file), password);
    }

    /**
     * retrieve certificate from a PKCS12 format file
     */
    public static Certificate retrieveCertFromPkcs12(File file, char[] password, String alias) throws Exception {
        KeyStore store = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
        store.load(new FileInputStream(file), password);
        return store.getCertificate(alias);
    }

    /**
     * store the certificate chain and client private key to a PKCS12 format file
     */
    public static void storeChainAsPkcs12(X509Certificate[] chain, PrivateKey privKey, char[] privKeyPassword, String alias, File file, char[] password) throws Exception {
        KeyStore store = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
        store.load(null, null);
        store.setKeyEntry(alias, privKey, privKeyPassword, chain);
        store.store(new FileOutputStream(file), password);
    }

    /**
     * retrieve the certificate chain from a PKCS12 format file
     */
    public static Certificate[] retrieveChainFromPkcs12(File file, char[] password, String alias) throws Exception {
        KeyStore store = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
        store.load(new FileInputStream(file), password);
        return store.getCertificateChain(alias);
    }

    /**
     * retrieve the key (usually private key) from a PKCS12 format file
     */
    public static Key retrievePrivateKeyFromPkcs12(File file, char[] password, String alias, char[] keyPassword) throws Exception {
        KeyStore store = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
        store.load(new FileInputStream(file), password);
        return store.getKey(alias, keyPassword);
    }

    /**
     * get KeyStore from a PKCS12 file
     */
    public static KeyStore loadKeyStore(File file, char[] password) throws Exception {
        KeyStore store = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
        store.load(new FileInputStream(file), password);
        return store;
    }
}
